// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.properties;

import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonShortcuts;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComponentWithBrowseButton;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.ui.EditorTextField;
import com.neuronbit.businessflow.intellij.dialog.TaskReturnFiledValueAssignToFlowReturnFieldTreeDialog;
import com.neuronbit.businessflow.intellij.editor.swing.editor.RadComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.RadJavaMethodComponent;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.InplaceContext;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.Property;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.PropertyEditor;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.PropertyRenderer;
import com.neuronbit.businessflow.intellij.editor.swing.editor.propertyInspector.renderers.LabelPropertyRenderer;
import com.neuronbit.businessflow.intellij.model.ParamValue;
import com.neuronbit.businessflow.intellij.model.Return;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;
import java.util.Set;

import static com.neuronbit.businessflow.intellij.util.PsiUtils.isAtomic;

/**
 * The type Field value to assign property.
 */
public final class TaskReturnTypeProperty extends Property<RadJavaMethodComponent, String> {
    private final PropertyRenderer myRenderer;
    private final MyEditor myEditor;
    private final Project project;
    private final String fieldName;
    private final PsiType fieldType;
    private final PsiType flowReturnType;

    /**
     * Instantiates a new Field value to assign property.
     *
     * @param parent         the parent
     * @param project        the project
     * @param fieldName      the field name
     * @param fieldType      the field type
     * @param flowReturnType the flow return type
     * @param name           the name
     */
    public TaskReturnTypeProperty(Property parent, Project project, String fieldName, PsiType fieldType, PsiType flowReturnType, String name) {
        super(parent, name);
        this.project = project;
        this.fieldName = fieldName;
        this.fieldType = fieldType;
        this.flowReturnType = flowReturnType;
        myRenderer = new LabelPropertyRenderer();
        myEditor = new MyEditor(project);
    }

    @Override
    public PropertyEditor<String> getEditor() {
        return myEditor;
    }

    @Override
    @NotNull
    public PropertyRenderer<String> getRenderer() {
        return myRenderer;
    }

    @Override
    public String getValue(final RadJavaMethodComponent component) {
        Set<Return> returns = component.getFlowReturns();
        if (null == returns) {
            return "";
        }
        for (Return aReturn : returns) {
            if (aReturn.getValues() == null) {
                continue;
            }
            for (ParamValue paramValue : aReturn.getValues()) {
                if (paramValue == null) {
                    continue;
                }
                //TODO shunyun 2024/4/16: 多级就比较难处理
                if ((component.getName() + "." + fieldName).equals(paramValue.getValue())) {
                    return aReturn.getName();
                }
            }
        }

        return "";
    }

    @Override
    protected void setValueImpl(final RadJavaMethodComponent component, final String value) {
        component.setFlowReturnValue(component.getName(), this.fieldName, value);
    }

    @Override
    public Property @NotNull [] getChildren(RadJavaMethodComponent component) {
        if (StringUtils.isNotBlank(getValue(component)) || fieldType instanceof PsiPrimitiveType) {
            return EMPTY_ARRAY;
        }

        PsiClass returnPsiClass = ReadAction.compute(() -> PsiTypesUtil.getPsiClass(fieldType));

        if (null == returnPsiClass || isAtomic(returnPsiClass)) {
            return EMPTY_ARRAY;
        }

        List<PsiField> fields = Arrays.stream(ReadAction.compute(returnPsiClass::getAllFields))
                //.filter(field -> ReadAction.compute(() -> returnPsiClass.findMethodsByName("set" + StringUtils.capitalize(ReadAction.compute(field::getName)), true)).length != 0)
                .toList();

        if (fields.isEmpty()) {
            return EMPTY_ARRAY;
        }

        Property[] properties = new Property[fields.size()];
        for (int i = 0; i < fields.size(); i++) {
            PsiField field = fields.get(i);
            String fieldName = ReadAction.compute(field::getName);
            PsiType fieldType = ReadAction.compute(field::getType);
            properties[i] = new TaskReturnTypeFieldProperty(this, component.getProject(), fieldName, fieldType, component.getFlowReturnType());
        }
        return properties;
    }

    private final class MyEditor extends PropertyEditor<String> {
        private final EditorTextField myEditorTextField;
        private final ComponentWithBrowseButton<EditorTextField> myTfWithButton;
        private Document myDocument;
        private String myInitialValue;
        private final Project myProject;
        private final MyActionListener myActionListener;

        /**
         * Instantiates a new My editor.
         *
         * @param project the project
         */
        MyEditor(final Project project) {
            myProject = project;
            myEditorTextField = new EditorTextField("", project, JavaFileType.INSTANCE) {
                @Override
                protected boolean shouldHaveBorder() {
                    return false;
                }
            };
            myActionListener = new MyActionListener();
            myTfWithButton = new ComponentWithBrowseButton<>(myEditorTextField, myActionListener);
            myEditorTextField.setBorder(null);
            new MyCancelEditingAction().registerCustomShortcutSet(CommonShortcuts.ESCAPE, myTfWithButton);
      /*
      myEditorTextField.addActionListener(
        new ActionListener() {
          public void actionPerformed(final ActionEvent e) {
            fireValueCommitted();
          }
        }
      );
      */
        }

        @Override
        public String getValue() {
            final String value = myDocument.getText();
            if (value.length() == 0 && myInitialValue == null) {
                return null;
            }
            return value.replace('$', '.'); // PSI works only with dots
        }

        @Override
        public JComponent getComponent(final RadComponent component, final String value, final InplaceContext inplaceContext) {
            myInitialValue = value;
            setEditorText(value != null ? value : "");
            myActionListener.setComponent(component);
            return myTfWithButton;
        }

        private void setEditorText(final String s) {
//            final JavaCodeFragmentFactory factory = JavaCodeFragmentFactory.getInstance(myProject);
//            PsiPackage defaultPackage = JavaPsiFacade.getInstance(myProject).findPackage("");
//            final PsiCodeFragment fragment = factory.createReferenceCodeFragment(s, defaultPackage, true, true);
//            myDocument = PsiDocumentManager.getInstance(myProject).getDocument(fragment);
            myDocument = new DocumentImpl(s);
            myEditorTextField.setDocument(myDocument);
        }

        @Override
        public void updateUI() {
            SwingUtilities.updateComponentTreeUI(myTfWithButton);
        }

        private final class MyActionListener implements ActionListener {
            /**
             * The My component.
             */
            RadComponent myComponent;

            /**
             * Sets component.
             *
             * @param component the component
             */
            public void setComponent(RadComponent component) {
                myComponent = component;
            }

            @Override
            public void actionPerformed(final ActionEvent e) {
                TaskReturnFiledValueAssignToFlowReturnFieldTreeDialog dialog = new TaskReturnFiledValueAssignToFlowReturnFieldTreeDialog(myComponent.getProject(),
                        flowReturnType,
                        fieldName,
                        fieldType);
                if (dialog.showAndGet()) {
                    String result = dialog.getSelected();
                    if (result != null) {
                        setEditorText(result);
                    }
                }
//                final String className = myEditorTextField.getText();
//        final PsiClass aClass = FormEditingUtil.findClassToBind(myComponent.getModule(), className);
//
//        final Project project = myComponent.getProject();
//        final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
//        final TreeClassChooser chooser = TreeClassChooserFactory.getInstance(project).createWithInnerClassesScopeChooser(
//          UIDesignerBundle.message("title.choose.class.to.bind"),
//          GlobalSearchScope.projectScope(project),
//          new ClassFilter() { // we need show classes from the sources roots only
//            @Override
//            public boolean isAccepted(final PsiClass aClass) {
//              final VirtualFile vFile = aClass.getContainingFile().getVirtualFile();
//              return vFile != null && fileIndex.isUnderSourceRootOfType(vFile, JavaModuleSourceRootTypes.SOURCES);
//            }
//          },
//          aClass
//        );
//        chooser.showDialog();
//
//        final PsiClass result = chooser.getSelected();
//        if (result != null) {
//          setEditorText(result.getQualifiedName());
//        }

                // todo[anton] make it via providing proper parent
                IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(() -> IdeFocusManager.getGlobalInstance().requestFocus(myEditorTextField, true));
            }
        }

        private final class MyCancelEditingAction extends AnAction {
            @Override
            public void actionPerformed(@NotNull final AnActionEvent e) {
                fireEditingCancelled();
            }
        }
    }
}
